import collections
import pc

class Parse():
    def __init__(self):
        self.ps = pc.ParserStack()
    
    def parseEverything(self):
        langPar = self.ps.parser(pc.pcRange(chr(0), chr(255)))
        return langPar
    
    def parseComment(self):                     #C
    # {T\C} 
        """commentPar = self.ps.parser(pc.pcWord('{')) \
                .parser(pc.butNot( self.parseEverything() , pc.pcWord('{') )) \
                .parser(pc.pcWord('}')) \
                .catens(3)
        # {C}
        commentPar = self.ps.parser(pc.pcWord('{')) \
                .delayed_parser(lambda: self.parseComment()) \
                .parser(pc.pcWord('}')) \
                .catens(3)
        #Union all above             
        commentPar = commentPar.disjs(2) \
                      .pop()"""  
        commentPar = self.ps.parser(pc.pcWord('{')) \
                .parser(self.parseEverything()) \
                .parser(pc.pcWord('}')) \
                .catens(3) \
                .pop()                                             
        return commentPar
    
    def parseBalanced(self,parsingMethod):       #N
        #WTW     
        balancedPar = self.ps.parser(self.parseWhiteSpaces()) \
                 .parser(parsingMethod()) \
                 .parser(self.parseWhiteSpaces()) \
                 .catens(3)         \
                 .pack(lambda x: x[1])                     
        #W(N)W
        balancedPar = balancedPar.parser(self.parseWhiteSpaces()) \
                .parser(pc.pcWord('(')) \
                .delayed_parser(lambda: self.parseBalanced(parsingMethod)) \
                .parser(pc.pcWord(')')) \
                .parser(self.parseWhiteSpaces()) \
                .catens(5) \
                .pack(lambda x: x[2])
        #W[N]W        
        balancedPar = balancedPar.parser(self.parseWhiteSpaces()) \
                .parser(pc.pcWord('[')) \
                .delayed_parser(lambda: self.parseBalanced(parsingMethod)) \
                .parser(pc.pcWord(']')) \
                .parser(self.parseWhiteSpaces()) \
                .catens(5) \
                .pack(lambda x: x[2])       
        #Union all above             
        balancedPar = balancedPar.disjs(3) \
                      .pop()
        return balancedPar    
    
    def parseDigit(self):                       #D
        digitPar = self.ps.parser(pc.pcOneOf('123456789')) \
                .parser(pc.pcOneOf('0123456789')) \
                .star()\
                .caten()\
                .pack(lambda x: x[0]+''.join(x[1]))\
                .pop()
        #print(digitPar)
        return digitPar
    
    def parseBigLetter(self):                   #B
        bigLetterPar = self.ps.parser(pc.pcOneOf('ABCDEFGHIJKLMNOPQRSTUVWXYZ')) \
                    .pop()
        #print(bigLetterPar)            
        return bigLetterPar
    
    def parseSmallLetter(self):                 #S
        smallLetterPar = self.ps.parser(pc.pcOneOf('abcdefghijklmnopqrstuvwxyz')) \
                    .star() \
                    .pop()
    #    print(smallLetterPar)
        return smallLetterPar
    
    def parseWhiteSpaces(self):                 #W
        #W
        whitePar = self.ps.parser(pc.pcRange(chr(0), chr(32))) \
                   .star() \
                   .pack(lambda x: '') \
                   .pop()        
        return whitePar
        """whitePar = self.ps.parser(pc.pcWord(' ')) \
                    .star() \
        #{T}                - in this case will ignore the things inside the comments!
        whitePar = whitePar.parser(self.parseComment()) \
        #Union all above                
        whitePar = whitePar.disjs(2) \
                   .pack(lambda x: '') \
                   .pop()        
        return whitePar"""
                                 
    def parseAtom(self):                        #A
        atomPar = self.ps.parser(self.parseBigLetter()) \
                    .parser(self.parseSmallLetter()) \
                    .caten() \
                    .pack(lambda x: x[0]+''.join(x[1])) \
                    .pack(lambda x: Atom(x)) \
                    .pop()
        #print(atomPar)            
        return atomPar
    
    def parseGroup(self):                       #G
        #AWG                
        groupPar = self.ps.parser(self.parseAtom()) \
                        .parser(self.parseWhiteSpaces()) \
                        .delayed_parser(lambda: self.parseGroup()) \
                        .catens(3)
        #MWG                  
        groupPar = groupPar.delayed_parser(lambda: self.parseMulti()) \
                        .parser(self.parseWhiteSpaces()) \
                        .delayed_parser(lambda: self.parseGroup()) \
                        .catens(3)
        #MWM               
        groupPar = groupPar.delayed_parser(lambda: self.parseMulti()) \
                        .parser(self.parseWhiteSpaces()) \
                        .delayed_parser(lambda: self.parseMulti()) \
                        .catens(3)
        #MWA                
        groupPar = groupPar.delayed_parser(lambda: self.parseMulti()) \
                        .parser(self.parseWhiteSpaces()) \
                        .parser(self.parseAtom()) \
                        .catens(3)
        #AWM                
        groupPar = groupPar.parser(self.parseAtom()) \
                        .parser(self.parseWhiteSpaces()) \
                        .delayed_parser(lambda: self.parseMulti()) \
                        .catens(3)
        #AWA
        groupPar = groupPar.parser(self.parseAtom()) \
                        .parser(self.parseWhiteSpaces()) \
                        .parser(self.parseAtom()) \
                        .catens(3)                
        #Union all above                
        groupPar = groupPar.disjs(6) \
                        .pack(lambda x: Group(x[0],x[2])) \
                        .pop()
        return groupPar
    
    def parseMulti(self):               #M
        #(WMW)WD         
        mulPar = self.ps.parser(pc.pcWord('(')) \
                .parser(self.parseWhiteSpaces()) \
                .delayed_parser(lambda: self.parseMulti()) \
                .parser(self.parseWhiteSpaces()) \
                .parser(pc.pcWord(')')) \
                .parser(self.parseWhiteSpaces()) \
                .parser(self.parseDigit()) \
                .catens(7) \
                .pack(lambda x: Multiplicity('(',x[2],')',x[6]))
        #[WMW]WD          
        mulPar = mulPar.parser(pc.pcWord('[')) \
                .parser(self.parseWhiteSpaces()) \
                .delayed_parser(lambda: self.parseMulti()) \
                .parser(self.parseWhiteSpaces()) \
                .parser(pc.pcWord(']')) \
                .parser(self.parseWhiteSpaces()) \
                .parser(self.parseDigit()) \
                .catens(7) \
                .pack(lambda x: Multiplicity('(',x[2],')',x[6]))        
        #(WGW)WD        
        mulPar = mulPar.parser(pc.pcWord('(')) \
                .parser(self.parseWhiteSpaces()) \
                .parser(self.parseGroup()) \
                .parser(self.parseWhiteSpaces()) \
                .parser(pc.pcWord(')')) \
                .parser(self.parseWhiteSpaces()) \
                .parser(self.parseDigit()) \
                .catens(7) \
                .pack(lambda x: Multiplicity('(',x[2],')',x[6]))
        #[WGW]WD        
        mulPar = mulPar.parser(pc.pcWord('[')) \
                .parser(self.parseWhiteSpaces()) \
                .parser(self.parseGroup()) \
                .parser(self.parseWhiteSpaces()) \
                .parser(pc.pcWord(']')) \
                .parser(self.parseWhiteSpaces()) \
                .parser(self.parseDigit()) \
                .catens(7) \
                .pack(lambda x: Multiplicity('(',x[2],')',x[6]))
        #AWD
        mulPar = mulPar.parser(self.parseAtom()) \
                .parser(self.parseWhiteSpaces()) \
                .parser(self.parseDigit()) \
                .catens(3) \
                .pack(lambda x: Multiplicity('',x[0],'',x[2]))        
        
        #Union all above           
        mulPar  = mulPar.disjs(5) \
                .pop()
        return mulPar
    
    def parseMol(self):                 #
        #WGW
        molPar = self.ps.parser(self.parseWhiteSpaces()) \
                 .parser(self.parseGroup()) \
                 .parser(self.parseWhiteSpaces()) \
                 .catens(3)
        #WMW         
        molPar =  molPar.parser(self.parseWhiteSpaces()) \
                 .parser(self.parseMulti()) \
                 .parser(self.parseWhiteSpaces()) \
                 .catens(3)
        #WAW         
        molPar =  molPar.parser(self.parseWhiteSpaces()) \
                 .parser(self.parseAtom()) \
                 .parser(self.parseWhiteSpaces()) \
                 .catens(3)
        #Union all above                   
        molPar =  molPar.disjs(3) \
                 .pack(lambda x: Molecule(x[1])) \
                 .pop()
        return molPar
    
    def startParsing(self):
        sol = self.ps.parser(self.parseBalanced(self.parseMol)) \
                .done()
        return sol


    
class Chem(collections.Container):
    
    @staticmethod
    def readMoleculeFromString(inputS):
        p = Parse()
        molPar = p.startParsing()
        (m,r) = molPar.match(inputS)
        print("SUCCESS - FINISHED")
        return (m,r)
                            
    def __contains__(self, x):
        pass
    
    def __str__(self):
        pass
    
    def computeWeight(self):
        pass
    
    def atoms(self):
        pass



class Atom(Chem):
    
    def __init__(self, strID):
        self.strID = strID
        self.atom = periodicTable.get(strID)
        if(self.atom == None):
                raise NoSuchAtom.exception(strID)
               
    def __contains__(self, x):
        return (self.strID == x.strID) 
    
    def __str__(self):
        return self.strID
    
    def computeWeight(self):
        return self.atom.get('atomic_weight')
    
    def atoms(self):
        return [self]    


        
class Multiplicity(Chem):
    
    def __init__(self,leftBracket,obj,rightBracket,num):
            self.leftBracket = leftBracket
            self.obj = obj
            self.rightBracket = rightBracket
            self.num = num
        
    def __contains__(self, x):
        return (self.obj.__contains__(x))
    
    def __str__(self):
        if(self.num == 1):
            return self.obj.__str__()
        else:
            return self.leftBracket.__str__()+self.obj.__str__()+self.rightBracket.__str__()+self.num.__str__()
    
    def computeWeight(self):
        return self.obj.computeWeight() * self.num
    
    def atoms(self):
        return self.obj.atoms()
    

    
class Group(Chem):
    
    def __init__(self,first,second):
        self.first = first
        self.second = second
        
    def __contains__(self, x):
        return (self.first.__contains__(x) or self.second.__contains__(x))
    
    def __str__(self):
        return self.first.__str__() + self.second.__str__()
    
    def computeWeight(self):
        return self.first.computeWeight() + self.second.computeWeight() 
    
    def atoms(self):
        return self.first.atoms() + self.second.atoms()
    

    
class Molecule(Chem):   
    
    def __init__(self,mol):
        self.mol = mol
           
    def __contains__(self, x):
        return self.mol.__contains__(x)
    
    def __str__(self):
        return self.mol.__str__()
    
    def computeWeight(self):
        return self.mol.computeWeight() 
    
    def atoms(self):
        return self.mol.atoms()
    

          
class NoSuchAtom(Exception):
    
    @staticmethod
    def exception(name):
        raise Exception('EXECPTION - No Such Atom - ' + name)
    

            
periodicTable = {
    'H': {'name': 'Hydrogen', 'atomic_number': 1, 'atomic_weight': 1.00794},
    'He': {'name': 'Helium', 'atomic_number': 2, 'atomic_weight': 4.0026},
    'Li': {'name': 'Lithium', 'atomic_number': 3, 'atomic_weight': 6.941},
    'Be': {'name': 'Beryllium', 'atomic_number': 4, 'atomic_weight': 9.0122},
    'B': {'name': 'Boron', 'atomic_number': 5, 'atomic_weight': 10.811},
    'C': {'name': 'Carbon', 'atomic_number': 6, 'atomic_weight': 12.0107},
    'N': {'name': 'Nitrogen', 'atomic_number': 7, 'atomic_weight': 14.0067},
    'O': {'name': 'Oxygen', 'atomic_number': 8, 'atomic_weight': 15.9994},
    'F': {'name': 'Fluorine', 'atomic_number': 9, 'atomic_weight': 18.9984},
    'Ne': {'name': 'Neon', 'atomic_number': 10, 'atomic_weight': 20.1797},
    'Na': {'name': 'Sodium', 'atomic_number': 11, 'atomic_weight': 22.9897},
    'Mg': {'name': 'Magnesium', 'atomic_number': 12, 'atomic_weight': 24.305},
    'Al': {'name': 'Aluminum', 'atomic_number': 13, 'atomic_weight': 26.9815},
    'Si': {'name': 'Silicon', 'atomic_number': 14, 'atomic_weight': 28.0855},
    'P': {'name': 'Phosphorus', 'atomic_number': 15, 'atomic_weight': 30.9738},
    'S': {'name': 'Sulfur', 'atomic_number': 16, 'atomic_weight': 32.065},
    'Cl': {'name': 'Chlorine', 'atomic_number': 17, 'atomic_weight': 35.453},
    'Ar': {'name': 'Argon', 'atomic_number': 18, 'atomic_weight': 39.948},
    'K': {'name': 'Potassium', 'atomic_number': 19, 'atomic_weight': 39.0983},
    'Ca': {'name': 'Calcium', 'atomic_number': 20, 'atomic_weight': 40.078},
    'Sc': {'name': 'Scandium', 'atomic_number': 21, 'atomic_weight': 44.9559},
    'Ti': {'name': 'Titanium', 'atomic_number': 22, 'atomic_weight': 47.867},
    'V': {'name': 'Vanadium', 'atomic_number': 23, 'atomic_weight': 50.9415},
    'Cr': {'name': 'Chromium', 'atomic_number': 24, 'atomic_weight': 51.9961},
    'Mn': {'name': 'Manganese', 'atomic_number': 25, 'atomic_weight': 54.938},
    'Fe': {'name': 'Iron', 'atomic_number': 26, 'atomic_weight': 55.845},
    'Co': {'name': 'Cobalt', 'atomic_number': 27, 'atomic_weight': 58.9332},
    'Ni': {'name': 'Nickel', 'atomic_number': 28, 'atomic_weight': 58.6934},
    'Cu': {'name': 'Copper', 'atomic_number': 29, 'atomic_weight': 63.546},
    'Zn': {'name': 'Zinc', 'atomic_number': 30, 'atomic_weight': 65.39},
    'Ga': {'name': 'Gallium', 'atomic_number': 31, 'atomic_weight': 69.723},
    'Ge': {'name': 'Germanium', 'atomic_number': 32, 'atomic_weight': 72.64},
    'As': {'name': 'Arsenic', 'atomic_number': 33, 'atomic_weight': 74.9216},
    'Se': {'name': 'Selenium', 'atomic_number': 34, 'atomic_weight': 78.96},
    'Br': {'name': 'Bromine', 'atomic_number': 35, 'atomic_weight': 79.904},
    'Kr': {'name': 'Krypton', 'atomic_number': 36, 'atomic_weight': 83.8},
    'Rb': {'name': 'Rubidium', 'atomic_number': 37, 'atomic_weight': 85.4678},
    'Sr': {'name': 'Strontium', 'atomic_number': 38, 'atomic_weight': 87.62},
    'Y': {'name': 'Yttrium', 'atomic_number': 39, 'atomic_weight': 88.9059},
    'Zr': {'name': 'Zirconium', 'atomic_number': 40, 'atomic_weight': 91.224},
    'Nb': {'name': 'Niobium', 'atomic_number': 41, 'atomic_weight': 92.9064},
    'Mo': {'name': 'Molybdenum', 'atomic_number': 42, 'atomic_weight': 95.94},
    'Tc': {'name': 'Technetium', 'atomic_number': 43, 'atomic_weight': 98},
    'Ru': {'name': 'Ruthenium', 'atomic_number': 44, 'atomic_weight': 101.07},
    'Rh': {'name': 'Rhodium', 'atomic_number': 45, 'atomic_weight': 102.9055},
    'Pd': {'name': 'Palladium', 'atomic_number': 46, 'atomic_weight': 106.42},
    'Ag': {'name': 'Silver', 'atomic_number': 47, 'atomic_weight': 107.8682},
    'Cd': {'name': 'Cadmium', 'atomic_number': 48, 'atomic_weight': 112.411},
    'In': {'name': 'Indium', 'atomic_number': 49, 'atomic_weight': 114.818},
    'Sn': {'name': 'Tin', 'atomic_number': 50, 'atomic_weight': 118.71},
    'Sb': {'name': 'Antimony', 'atomic_number': 51, 'atomic_weight': 121.76},
    'Te': {'name': 'Tellurium', 'atomic_number': 52, 'atomic_weight': 127.6},
    'I': {'name': 'Iodine', 'atomic_number': 53, 'atomic_weight': 126.9045},
    'Xe': {'name': 'Xenon', 'atomic_number': 54, 'atomic_weight': 131.293},
    'Cs': {'name': 'Cesium', 'atomic_number': 55, 'atomic_weight': 132.9055},
    'Ba': {'name': 'Barium', 'atomic_number': 56, 'atomic_weight': 137.327},
    'La': {'name': 'Lanthanum', 'atomic_number': 57, 'atomic_weight': 138.9055},
    'Ce': {'name': 'Cerium', 'atomic_number': 58, 'atomic_weight': 140.116},
    'Pr': {'name': 'Praseodymium', 'atomic_number': 59, 'atomic_weight': 140.9077},
    'Nd': {'name': 'Neodymium', 'atomic_number': 60, 'atomic_weight': 144.24},
    'Pm': {'name': 'Promethium', 'atomic_number': 61, 'atomic_weight': 145},
    'Sm': {'name': 'Samarium', 'atomic_number': 62, 'atomic_weight': 150.36},
    'Eu': {'name': 'Europium', 'atomic_number': 63, 'atomic_weight': 151.964},
    'Gd': {'name': 'Gadolinium', 'atomic_number': 64, 'atomic_weight': 157.25},
    'Tb': {'name': 'Terbium', 'atomic_number': 65, 'atomic_weight': 158.9253},
    'Dy': {'name': 'Dysprosium', 'atomic_number': 66, 'atomic_weight': 162.5},
    'Ho': {'name': 'Holmium', 'atomic_number': 67, 'atomic_weight': 164.9303},
    'Er': {'name': 'Erbium', 'atomic_number': 68, 'atomic_weight': 167.259},
    'Tm': {'name': 'Thulium', 'atomic_number': 69, 'atomic_weight': 168.9342},
    'Yb': {'name': 'Ytterbium', 'atomic_number': 70, 'atomic_weight': 173.04},
    'Lu': {'name': 'Lutetium', 'atomic_number': 71, 'atomic_weight': 174.967},
    'Hf': {'name': 'Hafnium', 'atomic_number': 72, 'atomic_weight': 178.49},
    'Ta': {'name': 'Tantalum', 'atomic_number': 73, 'atomic_weight': 180.9479},
    'W': {'name': 'Tungsten', 'atomic_number': 74, 'atomic_weight': 183.84},
    'Re': {'name': 'Rhenium', 'atomic_number': 75, 'atomic_weight': 186.207},
    'Os': {'name': 'Osmium', 'atomic_number': 76, 'atomic_weight': 190.23},
    'Ir': {'name': 'Iridium', 'atomic_number': 77, 'atomic_weight': 192.217},
    'Pt': {'name': 'Platinum', 'atomic_number': 78, 'atomic_weight': 195.078},
    'Au': {'name': 'Gold', 'atomic_number': 79, 'atomic_weight': 196.9665},
    'Hg': {'name': 'Mercury', 'atomic_number': 80, 'atomic_weight': 200.59},
    'Tl': {'name': 'Thallium', 'atomic_number': 81, 'atomic_weight': 204.3833},
    'Pb': {'name': 'Lead', 'atomic_number': 82, 'atomic_weight': 207.2},
    'Bi': {'name': 'Bismuth', 'atomic_number': 83, 'atomic_weight': 208.9804},
    'Po': {'name': 'Polonium', 'atomic_number': 84, 'atomic_weight': 208.982},
    'At': {'name': 'Astatine', 'atomic_number': 85, 'atomic_weight': 209.987},
    'Rn': {'name': 'Radon', 'atomic_number': 86, 'atomic_weight': 222},
    'Fr': {'name': 'Francium', 'atomic_number': 87, 'atomic_weight': 223},
    'Ra': {'name': 'Radium', 'atomic_number': 88, 'atomic_weight': 226},
    'Ac': {'name': 'Actinium', 'atomic_number': 89, 'atomic_weight': 227},
    'Th': {'name': 'Thorium', 'atomic_number': 90, 'atomic_weight': 232.0381},
    'Pa': {'name': 'Protactinium', 'atomic_number': 91, 'atomic_weight': 231.0359},
    'U': {'name': 'Uranium', 'atomic_number': 92, 'atomic_weight': 238.0289},
    'Np': {'name': 'Neptunium', 'atomic_number': 93, 'atomic_weight': 237},
    'Pu': {'name': 'Plutonium', 'atomic_number': 94, 'atomic_weight': 244.064},
    'Am': {'name': 'Americium', 'atomic_number': 95, 'atomic_weight': 243.061},
    'Cm': {'name': 'Curium', 'atomic_number': 96, 'atomic_weight': 247},
    'Bk': {'name': 'Berkelium', 'atomic_number': 97, 'atomic_weight': 247},
    'Cf': {'name': 'Californium', 'atomic_number': 98, 'atomic_weight': 251},
    'Es': {'name': 'Einsteinium', 'atomic_number': 99, 'atomic_weight': 252},
    'Fm': {'name': 'Fermium', 'atomic_number': 100, 'atomic_weight': 257},
    'Md': {'name': 'Mendelevium', 'atomic_number': 101, 'atomic_weight': 258},
    'No': {'name': 'Nobelium', 'atomic_number': 102, 'atomic_weight': 259},
    'Lr': {'name': 'Lawrencium', 'atomic_number': 103, 'atomic_weight': 262},
    'Rf': {'name': 'Rutherfordium', 'atomic_number': 104, 'atomic_weight': 261},
    'Db': {'name': 'Dubnium', 'atomic_number': 105, 'atomic_weight': 262},
    'Sg': {'name': 'Seaborgium', 'atomic_number': 106, 'atomic_weight': 266},
    'Bh': {'name': 'Bohrium', 'atomic_number': 107, 'atomic_weight': 264},
    'Hs': {'name': 'Hassium', 'atomic_number': 108, 'atomic_weight': 277},
    'Mt': {'name': 'Meitnerium', 'atomic_number': 109, 'atomic_weight': 268},
}



